package roboGP;

import java.io.FileWriter;
import java.io.IOException;

import robocode.BattleResults;
import robocode.control.*;
import robocode.control.events.*;

/**
 * Contiene i metodi per avviare il gioco e per raccogliere i risultati
 * delle battaglie.
 */
public class BattleRunner {

	RobocodeEngine engine;
	BattlefieldSpecification battlefield;
	BattleObserver battleObserver;
	static FileWriter writer;
	
	public BattleRunner() {
		engine = new RobocodeEngine(new java.io.File("robocode"));
		battleObserver = new BattleObserver();
		engine.addBattleListener(battleObserver);
		engine.setVisible(false);
		battlefield = new BattlefieldSpecification(800, 600);
	}
	
	/**
	 * Per ogni Metarobot avvia la battaglia contro tutti i robot avversari, raccogliendo i risultati e
	 * calcolando pe ciascun Metarobot la propria funzione di fitness.
	 * 
	 * @param metaRobots Una lista contenente i Metarobot.
	 * @param adverSet Una lista contenente gli avversari.
	 * @param samples Lista dei nomi dei robot avversari.
	 * @param rounds Numero di round di ogni battaglia.
	 */
	public void runBatchWithSAversaries(MetaRobot metaRobots[], String adverSet[], double[] weight, int rounds){
		BattleResults[] results;
		String metaBot, opponent;
		
		for(int i = 0; i < metaRobots.length; i++) {
			
			int robotScore[] = new int[adverSet.length];
			int totalScore[] = new int[adverSet.length];
			
			for(int j = 0; j < adverSet.length; j++){
				metaBot = EvolutionChamber.PACKAGE+"."+metaRobots[i].getName()+"*";
				opponent = adverSet[j];
				
				RobotSpecification[] selectedBots = engine.getLocalRepository(metaBot+", "+opponent);
				BattleSpecification battleSpec = new BattleSpecification(rounds, battlefield, selectedBots);
				System.out.println("\nBATTLE: "+metaBot+" vs "+opponent);
				engine.runBattle(battleSpec, true);
				
				results = battleObserver.getResults();
				System.out.println("SCORE: " +results[0].getTeamLeaderName()+" --> "+results[0].getScore());
				System.out.println("SCORE: " +results[1].getTeamLeaderName()+" --> "+results[1].getScore());
				
				robotScore[j] = results[0].getScore();
				totalScore[j] = results[0].getScore() + results[1].getScore();
				
			}	
			EvolutionChamber.metaRobots[i].setFitness(computeFitness(robotScore, totalScore, weight));
		}
	}
	
	/**
	 * Calcola la funzione di fitness per un robot.
	 * @param robotScore Il set dei punteggi di un robot contro ogni avversario.
	 * @param totalScore La somma dei punteggi del robot e dei suo avversario.
	 * @return La funzione di fitness.
	 */
	public double computeFitness(int robotScore[], int totalScore[], double[] weight)
	{		
		double fitness = 0;
		double weightSum = 0;
		for(int j=0;j<weight.length;j++)
			weightSum+=weight[j];
		
		for (int i = 0; i < robotScore.length; i++)
			fitness = fitness + ((double)robotScore[i]/totalScore[i])*weight[i];
		
		return fitness / weightSum;
	}
	
	/**
	 * Crea un nuovo file in cui salva i risultati delle battaglie.
	 * @param pop La grandezza della popolazione.
	 * @param ngen Il numero di generazioni.
	 * @param round Il numero di round per robot.
	 * @param tournSel Il numero di confronti in un Tournament Select.
	 * @param pcross La probabilità di crossover.
	 * @param pmut La probabilità di mutazione.
	 * @param psub La probabilità di mutazione con creazione di nuovo sottoalbero.
	 * @param pcopy La probabilità di copia.
	 */
	public static void newFileResults(int pop, int ngen, int round, int tournSel, double pcross, double pmut, double psub, double pcopy)
	{
		try {
			writer = new FileWriter("txt/results/test"+System.currentTimeMillis()+".csv");
			
			writer.append("Population Size");
		    writer.append(';');
		    writer.append("Generations Number");
		    writer.append(';');
		    writer.append("Round per Battle");
		    writer.append(';');
		    writer.append("Tournament Select");
		    writer.append(';');
		    writer.append("Crossover Probability");
		    writer.append(';');
		    writer.append("Mutation Probability");
		    writer.append(';');
		    writer.append("Subtree_Mutation Probability");
		    writer.append(';');
		    writer.append("Copy Probability");
		    writer.append('\n');
	 
		    writer.append(Integer.toString(pop));
		    writer.append(';');
		    writer.append(Integer.toString(ngen));
	        writer.append(';');
	        writer.append(Integer.toString(round));
	        writer.append(';');
	        writer.append(Integer.toString(tournSel));
	        writer.append(';');
		    writer.append(Double.toString(pcross));
		    writer.append(';');
		    writer.append(Double.toString(pmut));
		    writer.append(';');
		    writer.append(Double.toString(psub));
		    writer.append(';');
		    writer.append(Double.toString(pcopy));
	        writer.append('\n');writer.append('\n');
	        
	        writer.append("Generation Number");
			writer.append(';');
	        writer.append("Best fitness");
			writer.append(';');
		    writer.append("Average fitness");
		    writer.append(';');
		    writer.append("Worst fitness");
		    writer.append('\n');
		    
		    writer.flush();
		    
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Per ogni generazione salva le statistiche.
	 * @param genNumb
	 * @param bestFit
	 * @param avgFit
	 */
	public static void saveResults(int genNumb, double bestFit, double avgFit,double worstFit)
	{
		try {
			writer.append(Integer.toString(genNumb));
		    writer.append(';');
			writer.append(Double.toString(bestFit));
		    writer.append(';');
		    writer.append(Double.toString(avgFit));
		    writer.append(';');
		    writer.append(Double.toString(worstFit));
	        writer.append('\n');
	        
	        writer.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void closeResults(){
		try {
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

// based on example from Robocode Control API JavaDocs
class BattleObserver extends BattleAdaptor {
	
	robocode.BattleResults[] results;
	
	public void onBattleCompleted(BattleCompletedEvent e){
		results = e.getIndexedResults();
	}
	
	public void onBattleError(BattleErrorEvent e){
		System.out.println("Error running battle: " + e.getError());
	}
	
	public BattleResults[] getResults(){		
		return results;
	}
	
}
